package com.boarsoft.mongodb.core;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.net.SocketFactory;

import org.bson.Document;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;

import com.boarsoft.common.Util;
import com.mongodb.BasicDBObject;
import com.mongodb.DBDecoderFactory;
import com.mongodb.DBEncoderFactory;
import com.mongodb.DefaultDBDecoder;
import com.mongodb.DefaultDBEncoder;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ReadConcern;
import com.mongodb.ReadPreference;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;
import com.mongodb.client.FindIterable;
import com.mongodb.client.ListDatabasesIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
import com.mongodb.client.model.Filters;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.connection.ConnectionPoolSettings;
import com.mongodb.connection.ServerSettings;
import com.mongodb.connection.SocketSettings;
import com.mongodb.connection.SslSettings;
import com.mongodb.event.CommandListener;

/**
 * 通过MongoDB driver操作MongoDB基本实现
 * 
 * @author Mac_J
 *
 */
public class MongoDaoImpl implements MongoDao {

	protected String description;
	protected ReadPreference readPreference = ReadPreference.primary();
	protected WriteConcern writeConcern = WriteConcern.ACKNOWLEDGED;
	protected ReadConcern readConcern = ReadConcern.DEFAULT;
	protected CodecRegistry codecRegistry = MongoClient.getDefaultCodecRegistry();
	protected List<CommandListener> commandListeners = new ArrayList<CommandListener>();

	protected int minConnectionsPerHost;
	protected int maxConnectionsPerHost = 100;
	protected int threadsAllowedToBlockForConnectionMultiplier = 5;
	protected int serverSelectionTimeout = 1000 * 30;
	protected int maxWaitTime = 1000 * 60 * 2;
	protected int maxConnectionIdleTime;
	protected int maxConnectionLifeTime;
	protected int connectTimeout = 1000 * 10;
	protected int socketTimeout = 0;
	protected boolean socketKeepAlive = false;
	protected boolean sslEnabled = false;
	protected boolean sslInvalidHostNameAllowed = false;
	protected boolean alwaysUseMBeans = false;

	protected int heartbeatFrequency = 10000;
	protected int minHeartbeatFrequency = 500;
	protected int heartbeatConnectTimeout = 20000;
	protected int heartbeatSocketTimeout = 20000;
	protected int localThreshold = 15;

	protected String requiredReplicaSetName;
	protected DBDecoderFactory dbDecoderFactory = DefaultDBDecoder.FACTORY;
	protected DBEncoderFactory dbEncoderFactory = DefaultDBEncoder.FACTORY;
	protected SocketFactory socketFactory = SocketFactory.getDefault();
	protected boolean cursorFinalizerEnabled = true;

	protected ConnectionPoolSettings connectionPoolSettings;
	protected SocketSettings socketSettings;
	protected ServerSettings serverSettings;
	protected SocketSettings heartbeatSocketSettings;
	protected SslSettings sslSettings;
	private String username;
	private String database;
	private String password;

	protected List<ServerAddress> addrs;

	protected MongoClient mongoClient;

	@PostConstruct
	public void init() {
		MongoClientOptions.Builder builder = MongoClientOptions.builder();
		//
		builder.socketTimeout(socketTimeout);
		builder.socketKeepAlive(socketKeepAlive);
		builder.readPreference(readPreference);
		builder.writeConcern(writeConcern);
		builder.readConcern(readConcern);
		builder.codecRegistry(codecRegistry);
		builder.sslEnabled(sslEnabled);
		builder.sslInvalidHostNameAllowed(sslInvalidHostNameAllowed);
		builder.alwaysUseMBeans(alwaysUseMBeans);
		builder.heartbeatFrequency(heartbeatFrequency);
		builder.minHeartbeatFrequency(minHeartbeatFrequency);
		builder.heartbeatConnectTimeout(heartbeatConnectTimeout);
		builder.heartbeatSocketTimeout(heartbeatSocketTimeout);
		builder.localThreshold(localThreshold);
		builder.requiredReplicaSetName(requiredReplicaSetName);
		builder.dbDecoderFactory(dbDecoderFactory);
		builder.dbEncoderFactory(dbEncoderFactory);
		builder.socketFactory(socketFactory);
		builder.cursorFinalizerEnabled(cursorFinalizerEnabled);
		//
		for (CommandListener cl : commandListeners) {
			builder.addCommandListener(cl);
		}

		if (Util.strIsNotEmpty(username) && Util.strIsNotEmpty(password)) {
			MongoCredential credential = MongoCredential.createScramSha1Credential(username, database,
					password.toCharArray());
			List<MongoCredential> credentials = new ArrayList<MongoCredential>();
			for (int i = 0; i < addrs.size(); i++) {
				credentials.add(credential);
			}
			mongoClient = new MongoClient(addrs, credentials, builder.build());
			return;
		}
		mongoClient = new MongoClient(addrs, builder.build());
	}

	@Override
	public MongoDatabase getDatabase(String dbName) {
		return mongoClient.getDatabase(dbName);
	}

	@Override
	public MongoCollection<Document> getCollection(String dbName, String collName) {
		return mongoClient.getDatabase(dbName).getCollection(collName);
	}

	@Override
	public MongoIterable<String> getAllCollections(String dbName) {
		return getDatabase(dbName).listCollectionNames();
	}

	@Override
	public MongoIterable<String> listDatabaseNames() {
		return mongoClient.listDatabaseNames();
	}

	@Override
	public ListDatabasesIterable<Document> listDatabases() {
		return mongoClient.listDatabases();
	}

	@Override
	public void dropDatabase(String dbName) {
		this.getDatabase(dbName).drop();
	}

	@Override
	public Document find(MongoCollection<Document> coll, String id) {
		return coll.find(Filters.eq("_id", new ObjectId(id))).first();
	}

	@Override
	public Long count(MongoCollection<Document> coll) {
		return coll.count();
	}

	@Override
	public Long count(MongoCollection<Document> coll, Bson filter) {
		return coll.count(filter);
	}

	@Override
	public MongoCursor<Document> list(MongoCollection<Document> coll, Bson filter) {
		return coll.find(filter).iterator();
	}

	@Override
	public MongoCursor<Document> list(MongoCollection<Document> coll, Bson filter, String orderBy, int pageNo,
			int pageSize) {
		FindIterable<Document> lt = null;
		//
		if (filter == null) {
			lt = coll.find();
		} else {
			lt = coll.find(filter);
		}
		//
		Bson ob = new BasicDBObject(orderBy, -1);
		lt.sort(ob);
		return lt.skip((pageNo - 1) * pageSize).limit(pageSize).iterator();
	}

	@Override
	public int delete(MongoCollection<Document> coll, String id) {
		int count = 0;
		Bson filter = Filters.eq("_id", new ObjectId(id));
		DeleteResult deleteResult = coll.deleteOne(filter);
		count = (int) deleteResult.getDeletedCount();
		return count;
	}

	@Override
	public Document update(MongoCollection<Document> coll, String id, Document newdoc) {
		Bson filter = Filters.eq("_id", new ObjectId(id));
		coll.updateOne(filter, new Document("$set", newdoc));
		return newdoc;
	}

	@Override
	public Document replace(MongoCollection<Document> coll, String id, Document newdoc) {
		Bson filter = Filters.eq("_id", new ObjectId(id));
		coll.replaceOne(filter, newdoc);
		return newdoc;
	}

	@Override
	public void dropCollection(String dbName, String collName) {
		this.getDatabase(dbName).getCollection(collName).drop();
	}

	@Override
	public void close() {
		if (mongoClient == null) {
			return;
		}
		mongoClient.close();
		mongoClient = null;
	}

	@Override
	public void insert(String dbName, String collName, Document doc) {
		this.getCollection(dbName, collName).insertOne(doc);
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public ReadPreference getReadPreference() {
		return readPreference;
	}

	public void setReadPreference(ReadPreference readPreference) {
		this.readPreference = readPreference;
	}

	public WriteConcern getWriteConcern() {
		return writeConcern;
	}

	public void setWriteConcern(WriteConcern writeConcern) {
		this.writeConcern = writeConcern;
	}

	public ReadConcern getReadConcern() {
		return readConcern;
	}

	public void setReadConcern(ReadConcern readConcern) {
		this.readConcern = readConcern;
	}

	public CodecRegistry getCodecRegistry() {
		return codecRegistry;
	}

	public void setCodecRegistry(CodecRegistry codecRegistry) {
		this.codecRegistry = codecRegistry;
	}

	public List<CommandListener> getCommandListeners() {
		return commandListeners;
	}

	public void setCommandListeners(List<CommandListener> commandListeners) {
		this.commandListeners = commandListeners;
	}

	public int getMinConnectionsPerHost() {
		return minConnectionsPerHost;
	}

	public void setMinConnectionsPerHost(int minConnectionsPerHost) {
		this.minConnectionsPerHost = minConnectionsPerHost;
	}

	public int getMaxConnectionsPerHost() {
		return maxConnectionsPerHost;
	}

	public void setMaxConnectionsPerHost(int maxConnectionsPerHost) {
		this.maxConnectionsPerHost = maxConnectionsPerHost;
	}

	public int getThreadsAllowedToBlockForConnectionMultiplier() {
		return threadsAllowedToBlockForConnectionMultiplier;
	}

	public void setThreadsAllowedToBlockForConnectionMultiplier(int threadsAllowedToBlockForConnectionMultiplier) {
		this.threadsAllowedToBlockForConnectionMultiplier = threadsAllowedToBlockForConnectionMultiplier;
	}

	public int getServerSelectionTimeout() {
		return serverSelectionTimeout;
	}

	public void setServerSelectionTimeout(int serverSelectionTimeout) {
		this.serverSelectionTimeout = serverSelectionTimeout;
	}

	public int getMaxWaitTime() {
		return maxWaitTime;
	}

	public void setMaxWaitTime(int maxWaitTime) {
		this.maxWaitTime = maxWaitTime;
	}

	public int getMaxConnectionIdleTime() {
		return maxConnectionIdleTime;
	}

	public void setMaxConnectionIdleTime(int maxConnectionIdleTime) {
		this.maxConnectionIdleTime = maxConnectionIdleTime;
	}

	public int getMaxConnectionLifeTime() {
		return maxConnectionLifeTime;
	}

	public void setMaxConnectionLifeTime(int maxConnectionLifeTime) {
		this.maxConnectionLifeTime = maxConnectionLifeTime;
	}

	public int getConnectTimeout() {
		return connectTimeout;
	}

	public void setConnectTimeout(int connectTimeout) {
		this.connectTimeout = connectTimeout;
	}

	public int getSocketTimeout() {
		return socketTimeout;
	}

	public void setSocketTimeout(int socketTimeout) {
		this.socketTimeout = socketTimeout;
	}

	public boolean isSocketKeepAlive() {
		return socketKeepAlive;
	}

	public void setSocketKeepAlive(boolean socketKeepAlive) {
		this.socketKeepAlive = socketKeepAlive;
	}

	public boolean isSslEnabled() {
		return sslEnabled;
	}

	public void setSslEnabled(boolean sslEnabled) {
		this.sslEnabled = sslEnabled;
	}

	public boolean isSslInvalidHostNameAllowed() {
		return sslInvalidHostNameAllowed;
	}

	public void setSslInvalidHostNameAllowed(boolean sslInvalidHostNameAllowed) {
		this.sslInvalidHostNameAllowed = sslInvalidHostNameAllowed;
	}

	public boolean isAlwaysUseMBeans() {
		return alwaysUseMBeans;
	}

	public void setAlwaysUseMBeans(boolean alwaysUseMBeans) {
		this.alwaysUseMBeans = alwaysUseMBeans;
	}

	public int getHeartbeatFrequency() {
		return heartbeatFrequency;
	}

	public void setHeartbeatFrequency(int heartbeatFrequency) {
		this.heartbeatFrequency = heartbeatFrequency;
	}

	public int getMinHeartbeatFrequency() {
		return minHeartbeatFrequency;
	}

	public void setMinHeartbeatFrequency(int minHeartbeatFrequency) {
		this.minHeartbeatFrequency = minHeartbeatFrequency;
	}

	public int getHeartbeatConnectTimeout() {
		return heartbeatConnectTimeout;
	}

	public void setHeartbeatConnectTimeout(int heartbeatConnectTimeout) {
		this.heartbeatConnectTimeout = heartbeatConnectTimeout;
	}

	public int getHeartbeatSocketTimeout() {
		return heartbeatSocketTimeout;
	}

	public void setHeartbeatSocketTimeout(int heartbeatSocketTimeout) {
		this.heartbeatSocketTimeout = heartbeatSocketTimeout;
	}

	public int getLocalThreshold() {
		return localThreshold;
	}

	public void setLocalThreshold(int localThreshold) {
		this.localThreshold = localThreshold;
	}

	public String getRequiredReplicaSetName() {
		return requiredReplicaSetName;
	}

	public void setRequiredReplicaSetName(String requiredReplicaSetName) {
		this.requiredReplicaSetName = requiredReplicaSetName;
	}

	public DBDecoderFactory getDbDecoderFactory() {
		return dbDecoderFactory;
	}

	public void setDbDecoderFactory(DBDecoderFactory dbDecoderFactory) {
		this.dbDecoderFactory = dbDecoderFactory;
	}

	public DBEncoderFactory getDbEncoderFactory() {
		return dbEncoderFactory;
	}

	public void setDbEncoderFactory(DBEncoderFactory dbEncoderFactory) {
		this.dbEncoderFactory = dbEncoderFactory;
	}

	public SocketFactory getSocketFactory() {
		return socketFactory;
	}

	public void setSocketFactory(SocketFactory socketFactory) {
		this.socketFactory = socketFactory;
	}

	public boolean isCursorFinalizerEnabled() {
		return cursorFinalizerEnabled;
	}

	public void setCursorFinalizerEnabled(boolean cursorFinalizerEnabled) {
		this.cursorFinalizerEnabled = cursorFinalizerEnabled;
	}

	public ConnectionPoolSettings getConnectionPoolSettings() {
		return connectionPoolSettings;
	}

	public void setConnectionPoolSettings(ConnectionPoolSettings connectionPoolSettings) {
		this.connectionPoolSettings = connectionPoolSettings;
	}

	public SocketSettings getSocketSettings() {
		return socketSettings;
	}

	public void setSocketSettings(SocketSettings socketSettings) {
		this.socketSettings = socketSettings;
	}

	public ServerSettings getServerSettings() {
		return serverSettings;
	}

	public void setServerSettings(ServerSettings serverSettings) {
		this.serverSettings = serverSettings;
	}

	public SocketSettings getHeartbeatSocketSettings() {
		return heartbeatSocketSettings;
	}

	public void setHeartbeatSocketSettings(SocketSettings heartbeatSocketSettings) {
		this.heartbeatSocketSettings = heartbeatSocketSettings;
	}

	public SslSettings getSslSettings() {
		return sslSettings;
	}

	public void setSslSettings(SslSettings sslSettings) {
		this.sslSettings = sslSettings;
	}

	public List<ServerAddress> getAddrs() {
		return addrs;
	}

	public void setAddrs(List<ServerAddress> addrs) {
		this.addrs = addrs;
	}

	public MongoClient getMongoClient() {
		return mongoClient;
	}

	public void setMongoClient(MongoClient mongoClient) {
		this.mongoClient = mongoClient;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getDatabase() {
		return database;
	}

	public void setDatabase(String database) {
		this.database = database;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
}